'use strict';

/**
 * TicTacToeController
 */
function TicTacToeController(view) {
	this._view = view;
	this._match = {
		results : [],
		step : function() {
			return this.results.length;
		}
	};
}

TicTacToeController.prototype._setBoard = function(boardConfig) {
	this._board = new TBoard(boardConfig);
};

TicTacToeController.prototype.startup = function() {
	this._roundOver = false;

	if (this._board == null) {
		this._board = new TBoard();
	}

	switch (this._match.step()) {
	case 0:
		this._currentAI = new Francescone(this._board);
		break;
	case 1:
		this._currentAI = new Esat(this._board);
		break;
	case 2:
		this._currentAI = new Federico(this._board);
		break;
	default:
		this._currentAI = new Francescone(this._board);
	}

	this._view.init(this);
};

TicTacToeController.prototype._nextStep = function() {
	this._board = null;

	if (this._match.step() >= 3) {
		this._checkPuzzleResult();
	} else {
		this.startup();
	}
};

TicTacToeController.prototype._checkPuzzleResult = function() {
	var solved = true;
	var results = this._match.results;
	for (var i = 0; i < results.length; i++) {
		if (results[i] === Player.O) {
			solved = false;
			break;
		}
	}

	if (solved) {
		this._view.onSolved();
	} else {
		this._view.onFail();
	}
};

TicTacToeController.prototype.move = function(row, col) {
	if (this._roundOver) {
		return;
	}

	var board = this._board;
	var placed = board.squareAt(row, col).place(Player.O);
	if (placed) {
		var gameState = this._gameState();
		this._roundOver = gameState.roundOver;
		if (gameState.roundOver) {
			this._storeAndNotifyResult(gameState);
		} else {
			var aiMove = this._currentAI.nextMove();
			if (aiMove != null) {
				this._board.squareAt(aiMove.row, aiMove.col).place(Player.X);
				gameState = this._gameState();
				this._roundOver = gameState.roundOver;
				this._storeAndNotifyResult(gameState);
			}
		}
	}

	this._view.update(this._board);

	if (this._roundOver) {
		this._handleRoundOver();
	}
};

TicTacToeController.prototype._handleRoundOver = function() {
	setTimeout(function() {
		this._nextStep();
	}.bind(this), 1000);
};

TicTacToeController.prototype._gameState = function() {
	var board = this._board;
	var result = {
		stale : false,
		winner : null
	};

	var winner = board.winner();
	if (winner != null) {
		result.winner = winner;
	} else if (board.stale()) {
		result.stale = true;
	}

	result.roundOver = result.stale || result.winner !== null;

	return result;
};

TicTacToeController.prototype._storeAndNotifyResult = function(gameState) {
	if (gameState.stale) {
		this._view.onStale();
	} else if (gameState.winner !== null) {
		this._match.results.push(gameState.winner);
		this._view.onWinner(gameState.winner);
		this._view.updateScore(this._match);
	}
};

/**
 * Tic Tac Toe Board
 */
function TBoard(initialState) {
	this._state = initialState || [];
	this._state.length = 9;
	this._resultEvaluator = new ResultEvaluator(this);
	this.WIDTH = 3;
	this.HEIGHT = Math.ceil(this._state.length / this.WIDTH);
}

TBoard.prototype.state = function() {
	return this._state;
};

TBoard.prototype.rowFromIndex = function(index) {
	return Math.floor(index / this.WIDTH);
};

TBoard.prototype.colFromIndex = function(index) {
	return index % this.WIDTH;
};

TBoard.prototype.place = function(index, player) {
	var canPlace = this._state[index] == null;
	if (canPlace) {
		this._state[index] = player;
	}

	return canPlace;
};

TBoard.prototype.squareAt = function(row, col) {
	var self = this;
	return {
		place : function(player) {
			return self.place(self._toIndex(row, col), player);
		},
		player : function() {
			return self.playerAt(self._toIndex(row, col));
		}
	};
};

TBoard.prototype.playerAt = function(index) {
	return this._state[index];
};

TBoard.prototype._toIndex = function(row, col) {
	return row * this.WIDTH + col;
};

TBoard.prototype.stale = function() {
	return this._resultEvaluator.stale();
};

TBoard.prototype.winner = function() {
	return this._resultEvaluator.winner();
};

/**
 * BoardIteratorHelper
 */
function BoardIteratorHelper(board) {
	this._board = board;
}

BoardIteratorHelper.prototype._lineIterator = function(squareGetter) {
	var WIDTH = this._board.WIDTH;
	var index = 0;
	return {
		hasNext : function() {
			return index < WIDTH;
		},
		next : function() {
			var result = squareGetter(index);
			index++;
			return result;
		}
	};
};

BoardIteratorHelper.prototype.rowsIterator = function() {
	return this._0NIterator(this._board.HEIGHT, function(col) {
		return this.rowIterator(col);
	}.bind(this));
};

BoardIteratorHelper.prototype._0NIterator = function(max, callback) {
	var index = -1;
	return {
		hasNext : function() {
			return index < max;
		},
		next : function() {
			return callback(++index);
		}
	};
};

BoardIteratorHelper.prototype.rowIterator = function(col) {
	return this._lineIterator(function(row) {
		return this._board.squareAt(row, col);
	}.bind(this));
};

BoardIteratorHelper.prototype.colIterator = function(row) {
	return this._lineIterator(function(col) {
		return this._board.squareAt(row, col);
	}.bind(this));
};

BoardIteratorHelper.prototype.lrDiagonalIterator = function() {
	return this._lineIterator(function(i) {
		return this._board.squareAt(i, i);
	}.bind(this));
};

BoardIteratorHelper.prototype.rlDiagonalIterator = function() {
	return this._lineIterator(function(i) {
		return this._board.squareAt(i, 2 - i);
	}.bind(this));
};

/**
 * ResultEvaluator
 */
function ResultEvaluator(board) {
	this._board = board;
	this._iteratorHelper = new BoardIteratorHelper(board);
}

ResultEvaluator.prototype.stale = function() {
	var stale = true;
	var squares = this._board._state;

	for (var i = 0; i < squares.length; i++) {
		if (squares[i] == null) {
			stale = false;
			break;
		}
	}

	return stale;
};

ResultEvaluator.prototype.winner = function() {
	var result = null;

	result = this._evaluateRows();
	if (result != null) {
		return result;
	}

	result = this._evaluateCols();
	if (result != null) {
		return result;
	}

	result = this._evaluateDiagonals();
	if (result != null) {
		return result;
	}

	return null;
};

ResultEvaluator.prototype._evaluateRows = function() {
	var WIDTH = this._board.WIDTH;
	for (var row = 0; row < WIDTH; row++) {
		var result = this._threeContiguos(this._iteratorHelper.colIterator(row));
		if (result != null) {
			return result;
		}
	}

	return null;
};

ResultEvaluator.prototype._evaluateCols = function() {
	var WIDTH = this._board.WIDTH;
	for (var col = 0; col < WIDTH; col++) {
		var result = this._threeContiguos(this._iteratorHelper.rowIterator(col));
		if (result != null) {
			return result;
		}
	}

	return null;
};

ResultEvaluator.prototype._evaluateDiagonals = function() {
	var result = this._threeContiguos(this._iteratorHelper.lrDiagonalIterator());
	if (result != null) {
		return result;
	}

	result = this._threeContiguos(this._iteratorHelper.rlDiagonalIterator());
	if (result != null) {
		return result;
	}

	return null;
};

ResultEvaluator.prototype._threeContiguos = function(iterator) {
	var count = 0;
	var lastPlayer = null;
	while (iterator.hasNext()) {
		var player = iterator.next().player();
		if (player === lastPlayer && player != null) {
			count++;

			if (count >= 2) {
				return player;
			}
		}

		lastPlayer = player;
	}

	return null;
};

/**
 * Player
 */
function Player() {
}

Player.X = new function X() {
	this.toString = function() {
		return 'X';
	};

	this.valueOf = function() {
		return 'X';
	};
};

Player.O = new function O() {
	this.toString = function() {
		return 'O';
	};

	this.valueOf = function() {
		return 'O';
	};
};

/**
 * Francescone AI player
 */
function Francescone(board) {
	this._board = board;
}

Francescone.prototype.nextMove = function() {
	var freeSquares = [];

	var board = this._board.state();
	for (var i = 0; i < board.length; i++) {
		var square = board[i];
		if (square == null) {
			freeSquares.push(i);
		}
	}

	var canMove = freeSquares.length > 0;
	if (canMove) {
		var randomIndex = Math.floor((Math.random() * freeSquares.length));
		var randomPosition = freeSquares[randomIndex];
		return {
			row : this._board.rowFromIndex(randomPosition),
			col : this._board.colFromIndex(randomPosition),
		};
	}

	return null;
};

/**
 * Esat AI player
 */
function Esat(board) {
	this._board = board;
}

Esat.prototype = Object.create(Francescone.prototype);
Esat.prototype.constructor = Esat;

Esat.prototype.nextMove = function() {
	return Francescone.prototype.nextMove.call(this);
};

/**
 * Federico AI player
 */
function Federico(board) {
	this._board = board;
}

Federico.prototype = Object.create(Francescone.prototype);
Federico.prototype.constructor = Federico;

Federico.prototype.nextMove = function() {
	return Francescone.prototype.nextMove.call(this);
};

/**
 * Heuristic
 */
function Heuristic(board, player) {
	this._board = board;
	this._player = player;
	this._boardIterator = new BoardIteratorHelper(this._board);
}

Heuristic.prototype.evaluate = function() {
	var rowsIt = this._boardIterator.rowsIterator();
	var result = 0;
	while (rowsIt.hasNext()) {
		var rowIt = rowsIt.next();
		var count = 0;
		while (rowIt.hasNext()) {
			var player = rowIt.next().player();
			if (player !== null && this._player === player) {
				count++;
			}
		}
		var evaluation = Math.pow(10, count - 1);
		if (evaluation < 1) {
			evaluation = 0;
		}

		result += evaluation;
	}

	return result;
};
